//电子邮件puhalskijsemen@gmail.com
//源码网站 开vpn全局模式打开 http://web3incubators.com/
//电报https://t.me/gamecode999
//网页客服 http://web3incubators.com/kefu.html



import ListItem from './ListItem';
import { ResourceUtil } from './ResUtil';

const { ccclass, property, disallowMultiple, menu, executionOrder, requireComponent } = cc._decorator;

/** 建议类名：ListComponent */
enum TemplateType {
    NODE = 1,
    PREFAB = 2
}

/** 建议类名：SlideMode */
enum SlideMode {
    NORMAL = 1,
    ADHERING = 2,
    PAGE = 3
}

/** 建议类名：SelectedMode */
enum SelectedMode {
    NONE = 0,
    SINGLE = 1,
    MULT = 2
}

/** 建议类名：ListComponent */
@ccclass
@disallowMultiple
@menu("UI组件/List Extent")
@requireComponent(cc.ScrollView)
@executionOrder(-5000)
export default class ListComponent extends cc.Component {
    @property({ type: cc.Enum(TemplateType) })
    templateType: TemplateType = TemplateType.NODE;

    @property({ type: cc.Node, visible() { return this.templateType === TemplateType.NODE; } })
    tmpNode: cc.Node | null = null;

    @property({ type: cc.Prefab, visible() { return this.templateType === TemplateType.PREFAB; } })
    tmpPrefab: cc.Prefab | null = null;

    @property({ type: cc.Enum(SlideMode) })
    private _slideMode: SlideMode = SlideMode.NORMAL;

    @property({ type: cc.Float, range: [0, 1, 0.1], slide: true, visible() { return this._slideMode === SlideMode.PAGE; } })
    pageDistance: number = 0.3;

    @property({ type: cc.Component.EventHandler, visible() { return this._slideMode === SlideMode.PAGE; } })
    pageChangeEvent: cc.Component.EventHandler = new cc.Component.EventHandler();

    // @property({ type: cc.Boolean })
    // private _virtual: boolean = true;

    @property()
    _virtual: boolean = true;

    @property({ visible() { return this.slideMode === SlideMode.NORMAL; } })
    cyclic: boolean = false;

    @property({ visible() { return this.virtual; } })
    lackCenter: boolean = false;

    @property({ visible() { return this.virtual && !this.lackCenter; } })
    lackSlide: boolean = false;

    @property({ type: cc.Integer, range: [0, 6, 1], slide: true })
    private _updateRate: number = 0;

    // @property({ type: cc.Integer, range: [0, 6, 1], slide: true })
    // updateRate: number = 0;

    @property({ type: cc.Integer, range: [0, 12, 1], slide: true })
    frameByFrameRenderNum: number = 0;

    @property({ type: cc.Component.EventHandler })
    renderEvent: cc.Component.EventHandler = new cc.Component.EventHandler();

    @property({ type: cc.Enum(SelectedMode) })
    selectedMode: SelectedMode = SelectedMode.NONE;

    @property({ visible() { return this.selectedMode === SelectedMode.SINGLE; } })
    repeatEventSingle: boolean = false;

    @property({ type: cc.Component.EventHandler, visible() { return this.selectedMode > SelectedMode.NONE; } })
    selectedEvent: cc.Component.EventHandler = new cc.Component.EventHandler();

    @property({ serializable: false })
    private _numItems: number = 0;

    private _selectedId: number = -1;
    private _forceUpdate: boolean = false;
    private _updateDone: boolean = true;
    private _inited: boolean = false;
    private _needUpdateWidget: boolean = false;
    private _aniDelRuning: boolean = false;
    private _doneAfterUpdate: boolean = false;
    private adhering: boolean = false;
    private _adheringBarrier: boolean = false;
    private curPageNum: number = 0;
    private _scrollView: cc.ScrollView | null = null;
    private content: cc.Node | null = null;
    private _layout: cc.Layout | null = null;
    private _align: cc.Layout.Type = cc.Layout.Type.HORIZONTAL;
    private _resizeMode: cc.Layout.ResizeMode = cc.Layout.ResizeMode.NONE;
    private _startAxis: cc.Layout.AxisDirection = cc.Layout.AxisDirection.HORIZONTAL;
    private _topGap: number = 0;
    private _rightGap: number = 0;
    private _bottomGap: number = 0;
    private _leftGap: number = 0;
    private _columnGap: number = 0;
    private _lineGap: number = 0;
    private _colLineNum: number = 0;
    private _verticalDir: cc.Layout.VerticalDirection = cc.Layout.VerticalDirection.TOP_TO_BOTTOM;
    private _horizontalDir: cc.Layout.HorizontalDirection = cc.Layout.HorizontalDirection.LEFT_TO_RIGHT;
    private _itemTmp: cc.Node | null = null;
    private _itemSize: cc.Size = cc.size(0, 0);
    private _pool: cc.NodePool = new cc.NodePool();
    private _lastDisplayData: any[] = [];
    private displayData: any[] = [];
    private _updateCounter: number = 0;
    private _scrollItem: cc.Node | null = null;
    private _beganPos: number | null = null;
    private _scrollPos: number | null = null;
    private _scrollToListId: number | null = null;
    private _scrollToEndTime: number | null = null;
    private _scrollToSo: any = null;
    private _customSize: any = null;
    private _actualNumItems: number = 0;
    private _cyclicNum: number = 0;
    private _cyclicPos1: number = 0;
    private _cyclicPos2: number = 0;
    private _cyclicAllItemSize: number = 0;
    private _cycilcAllItemSizeNoEdge: number = 0;
    private _allItemSize: number = 0;
    private _allItemSizeNoEdge: number = 0;
    private _alignCalcType: number = 0;
    private _lastSelectedId: number | null = null;
    private multSelected: number[] = [];
    private nearestListId: number | null = null;
    private elasticTop: number = 0;
    private elasticRight: number = 0;
    private elasticBottom: number = 0;
    private elasticLeft: number = 0;
    private viewTop: number = 0;
    private viewRight: number = 0;
    private viewBottom: number = 0;
    private viewLeft: number = 0;
    private displayItemNum: number = 0;
    private firstListId: number = 0;

    //add新增
    _aniDelItem: cc.Node | null = null;
    _aniDelBeforePos: cc.Vec3 | null = null;
    _aniDelBeforeScale: number = 1;
    _aniDelCB: Function | null = null;
    _sizeType: boolean = false;
    _lack: boolean = false;
    frameCount: number = 0;
    curScrollIsTouch: boolean = false;
    scrollToListId: number = 0;

    get slideMode(): SlideMode {
        return this._slideMode;
    }

    set slideMode(value: SlideMode) {
        this._slideMode = value;
    }

    get virtual(): boolean {
        return this._virtual;
    }

    set virtual(value: boolean) {
        if (value != null) {
            this._virtual = value;
        }
        if (this._numItems !== 0) {
            this._onScrolling();
        }
    }

    get updateRate(): number {
        return this._updateRate;
    }

    set updateRate(value: number) {
        if (value >= 0 && value <= 6) {
            this._updateRate = value;
        }
    }

    get selectedId(): number {
        return this._selectedId;
    }

    set selectedId(value: number) {
        let item: cc.Node | null;
        switch (this.selectedMode) {
            case SelectedMode.SINGLE:
                if (!this.repeatEventSingle && value === this._selectedId) {
                    return;
                }
                item = this.getItemByListId(value);
                let listItem: ListItem | null;
                if (this._selectedId >= 0) {
                    this._lastSelectedId = this._selectedId;
                } else {
                    this._lastSelectedId = null;
                }
                this._selectedId = value;
                if (item) {
                    listItem = item.getComponent(ListItem);
                    if (listItem) {
                        listItem.selected = true;
                    }
                }
                if (this._lastSelectedId >= 0 && this._lastSelectedId !== this._selectedId) {
                    const lastItem = this.getItemByListId(this._lastSelectedId);
                    if (lastItem) {
                        lastItem.getComponent(ListItem).selected = false;
                    }
                }
                if (this.selectedEvent) {
                    cc.Component.EventHandler.emitEvents(
                        [this.selectedEvent],
                        item,
                        value % this._actualNumItems,
                        this._lastSelectedId == null ? null : this._lastSelectedId % this._actualNumItems
                    );
                }
                break;
            case SelectedMode.MULT:
                item = this.getItemByListId(value);
                if (!item) {
                    return;
                }
                listItem = item.getComponent(ListItem);
                if (this._selectedId >= 0) {
                    this._lastSelectedId = this._selectedId;
                }
                this._selectedId = value;
                const isSelected = !listItem.selected;
                listItem.selected = isSelected;
                const index = this.multSelected.indexOf(value);
                if (isSelected && index < 0) {
                    this.multSelected.push(value);
                } else if (!isSelected && index >= 0) {
                    this.multSelected.splice(index, 1);
                }
                if (this.selectedEvent) {
                    cc.Component.EventHandler.emitEvents(
                        [this.selectedEvent],
                        item,
                        value % this._actualNumItems,
                        this._lastSelectedId == null ? null : this._lastSelectedId % this._actualNumItems,
                        isSelected
                    );
                }
                break;
        }
    }

    get numItems(): number {
        return this._actualNumItems;
    }

    set numItems(value: number) {
        if (this.checkInited(false)) {
            if (value == null || value < 0) {
                cc.error("numItems set the wrong::", value);
            } else {
                this._actualNumItems = this._numItems = value;
                this._forceUpdate = true;
                if (this._virtual) {
                    this._resizeContent();
                    if (this.cyclic) {
                        this._numItems = this._cyclicNum * this._numItems;
                    }
                    this._onScrolling();
                    if (!this.frameByFrameRenderNum && this.slideMode === SlideMode.PAGE) {
                        this.curPageNum = this.nearestListId;
                    }
                } else {
                    if (this.cyclic) {
                        this._resizeContent();
                        this._numItems = this._cyclicNum * this._numItems;
                    }
                    const layout = this.content.getComponent(cc.Layout);
                    if (layout) {
                        layout.enabled = true;
                    }
                    this._delRedundantItem();
                    this.firstListId = 0;
                    if (this.frameByFrameRenderNum > 0) {
                        let renderNum = this.frameByFrameRenderNum > this._numItems ? this._numItems : this.frameByFrameRenderNum;
                        for (let i = 0; i < renderNum; i++) {
                            this._createOrUpdateItem2(i);
                        }
                        if (this.frameByFrameRenderNum < this._numItems) {
                            this._updateCounter = this.frameByFrameRenderNum;
                            this._updateDone = false;
                        }
                    } else {
                        for (let i = 0; i < this._numItems; i++) {
                            this._createOrUpdateItem2(i);
                        }
                        this.displayItemNum = this._numItems;
                    }
                }
            }
        }
    }

    get scrollView(): cc.ScrollView {
        return this._scrollView;
    }

    onLoad() {
        this._init();
    }

    onDestroy() {
        if (cc.isValid(this._itemTmp)) {
            this._itemTmp.destroy();
        }
        if (cc.isValid(this.tmpNode)) {
            this.tmpNode.destroy();
        }
        if (this._pool) {
            this._pool.clear();
        }
    }

    onEnable() {
        this._registerEvent();
        this._init();
        if (this._aniDelRuning) {
            this._aniDelRuning = false;
            if (this._aniDelItem) {
                if (this._aniDelBeforePos) {
                    this._aniDelItem.position = this._aniDelBeforePos;
                    delete this._aniDelBeforePos;
                }
                if (this._aniDelBeforeScale) {
                    this._aniDelItem.scale = this._aniDelBeforeScale;
                    delete this._aniDelBeforeScale;
                }
                delete this._aniDelItem;
            }
            if (this._aniDelCB) {
                this._aniDelCB();
                delete this._aniDelCB;
            }
        }
    }

    onDisable() {
        this._unregisterEvent();
    }

    private _registerEvent() {
        this.node.on(cc.Node.EventType.TOUCH_START, this._onTouchStart, this, true);
        this.node.on("touch-up", this._onTouchUp, this);
        this.node.on(cc.Node.EventType.TOUCH_CANCEL, this._onTouchCancelled, this, true);
        this.node.on("scroll-began", this._onScrollBegan, this, true);
        this.node.on("scroll-ended", this._onScrollEnded, this, true);
        this.node.on("scrolling", this._onScrolling, this, true);
        this.node.on(cc.Node.EventType.SIZE_CHANGED, this._onSizeChanged, this);
    }

    private _unregisterEvent() {
        this.node.off(cc.Node.EventType.TOUCH_START, this._onTouchStart, this, true);
        this.node.off("touch-up", this._onTouchUp, this);
        this.node.off(cc.Node.EventType.TOUCH_CANCEL, this._onTouchCancelled, this, true);
        this.node.off("scroll-began", this._onScrollBegan, this, true);
        this.node.off("scroll-ended", this._onScrollEnded, this, true);
        this.node.off("scrolling", this._onScrolling, this, true);
        this.node.off(cc.Node.EventType.SIZE_CHANGED, this._onSizeChanged, this);
    }

    private _init() {
        if (!this._inited) {
            this._scrollView = this.node.getComponent(cc.ScrollView);
            this.content = this._scrollView.content;
            if (this.content) {
                this._layout = this.content.getComponent(cc.Layout);
                this._align = this._layout.type;
                this._resizeMode = this._layout.resizeMode;
                this._startAxis = this._layout.startAxis;
                this._topGap = this._layout.paddingTop;
                this._rightGap = this._layout.paddingRight;
                this._bottomGap = this._layout.paddingBottom;
                this._leftGap = this._layout.paddingLeft;
                this._columnGap = this._layout.spacingX;
                this._lineGap = this._layout.spacingY;
                this._colLineNum = 0;
                this._verticalDir = this._layout.verticalDirection;
                this._horizontalDir = this._layout.horizontalDirection;
                this.setTemplateItem(ResourceUtil.instantiate(this.templateType === TemplateType.PREFAB ? this.tmpPrefab : this.tmpNode));
                if (this._slideMode === SlideMode.ADHERING || this._slideMode === SlideMode.PAGE) {
                    this._scrollView.inertia = false;
                    this._scrollView["_onMouseWheel"] = function () { };
                }
                if (!this.virtual) {
                    this.lackCenter = false;
                }
                this._lastDisplayData = [];
                this.displayData = [];
                this._pool = new cc.NodePool();
                this._forceUpdate = false;
                this._updateCounter = 0;
                this._updateDone = true;
                this.curPageNum = 0;
                if (this.cyclic) {
                    // this._scrollView._processAutoScrolling = this._processAutoScrolling.bind(this);
                    this._scrollView["_processAutoScrolling"] = this._processAutoScrolling.bind(this);
                    // this._scrollView._startBounceBackIfNeeded = function () {
                    this._scrollView["_startBounceBackIfNeeded"] = function () {
                        return false;
                    };
                }
                switch (this._align) {
                    case cc.Layout.Type.HORIZONTAL:
                        switch (this._horizontalDir) {
                            case cc.Layout.HorizontalDirection.LEFT_TO_RIGHT:
                                this._alignCalcType = 1;
                                break;
                            case cc.Layout.HorizontalDirection.RIGHT_TO_LEFT:
                                this._alignCalcType = 2;
                        }
                        break;
                    case cc.Layout.Type.VERTICAL:
                        switch (this._verticalDir) {
                            case cc.Layout.VerticalDirection.TOP_TO_BOTTOM:
                                this._alignCalcType = 3;
                                break;
                            case cc.Layout.VerticalDirection.BOTTOM_TO_TOP:
                                this._alignCalcType = 4;
                        }
                        break;
                    case cc.Layout.Type.GRID:
                        switch (this._startAxis) {
                            case cc.Layout.AxisDirection.HORIZONTAL:
                                switch (this._verticalDir) {
                                    case cc.Layout.VerticalDirection.TOP_TO_BOTTOM:
                                        this._alignCalcType = 3;
                                        break;
                                    case cc.Layout.VerticalDirection.BOTTOM_TO_TOP:
                                        this._alignCalcType = 4;
                                }
                                break;
                            case cc.Layout.AxisDirection.VERTICAL:
                                switch (this._horizontalDir) {
                                    case cc.Layout.HorizontalDirection.LEFT_TO_RIGHT:
                                        this._alignCalcType = 1;
                                        break;
                                    case cc.Layout.HorizontalDirection.RIGHT_TO_LEFT:
                                        this._alignCalcType = 2;
                                }
                        }
                }
                ResourceUtil.removeAllChildrenAndRelease(this.content);
                this._inited = true;
            } else {
                cc.error(this.node.name + "'s cc.ScrollView unset content!");
            }
        }
    }

    private _processAutoScrolling(dt: number) {
        //this._scrollView._autoScrollAccumulatedTime += dt;
        this._scrollView["_autoScrollAccumulatedTime"] += dt;
        let percentage = Math.min(1, this._scrollView["_autoScrollAccumulatedTime"] / this._scrollView["_autoScrollTotalTime"]);
        if (this._scrollView["_autoScrollAttenuate"]) {
            const time = percentage - 1;
            percentage = time * time * time * time * time + 1;
        }
        const position = this._scrollView["_autoScrollStartPosition"].add(this._scrollView["_autoScrollTargetDelta"].mul(percentage));
        const threshold = this._scrollView["getScrollEndedEventTiming"]();
        const isScrollEnded = Math.abs(percentage - 1) <= threshold;
        if (isScrollEnded && !this._scrollView["_isScrollEndedWithThresholdEventFired"]) {
            this._scrollView["_dispatchEvent"]("scroll-ended-with-threshold");
            this._scrollView["_isScrollEndedWithThresholdEventFired"] = true;
        }
        if (isScrollEnded) {
            this._scrollView["_autoScrolling"] = false;
        }
        const delta = position.sub(this._scrollView.getContentPosition());
        this._scrollView["_moveContent"](this._scrollView["_clampDelta"](delta), isScrollEnded);
        this._scrollView["_dispatchEvent"]("scrolling");
        if (!this._scrollView["_autoScrolling"]) {
            this._scrollView["_isBouncing"] = false;
            this._scrollView["_scrolling"] = false;
            this._scrollView["_dispatchEvent"]("scroll-ended");
        }
    }

    setTemplateItem(item: cc.Node) {
        if (item) {
            this._itemTmp = item;
            if (this._resizeMode === cc.Layout.ResizeMode.CHILDREN) {
                this._itemSize = this._layout.cellSize;
            } else {
                this._itemSize = cc.size(item.width, item.height);
            }
            const listItem = item.getComponent(ListItem);
            if (!listItem) {
                this.selectedMode = SelectedMode.NONE;
            }
            const widget = item.getComponent(cc.Widget);
            if (widget && widget.enabled) {
                this._needUpdateWidget = true;
            }
            if (this.selectedMode === SelectedMode.MULT) {
                this.multSelected = [];
            }
            switch (this._align) {
                case cc.Layout.Type.HORIZONTAL:
                    this._colLineNum = 1;
                    this._sizeType = false;
                    break;
                case cc.Layout.Type.VERTICAL:
                    this._colLineNum = 1;
                    this._sizeType = true;
                    break;
                case cc.Layout.Type.GRID:
                    if (this._startAxis === cc.Layout.AxisDirection.HORIZONTAL) {
                        const width = this.content.width - this._leftGap - this._rightGap;
                        this._colLineNum = Math.floor((width + this._columnGap) / (this._itemSize.width + this._columnGap));
                        this._sizeType = true;
                    } else {
                        const height = this.content.height - this._topGap - this._bottomGap;
                        this._colLineNum = Math.floor((height + this._lineGap) / (this._itemSize.height + this._lineGap));
                        this._sizeType = false;
                    }
            }
        }
    }

    checkInited(showError: boolean = true): boolean {
        if (!this._inited && showError) {
            cc.error("List initialization not completed!");
        }
        return this._inited;
    }

    private _resizeContent() {
        let size: number;
        switch (this._align) {
            case cc.Layout.Type.HORIZONTAL:
                if (this._customSize) {
                    const fixedSize = this._getFixedSize(null);
                    size = this._leftGap + fixedSize.val + this._itemSize.width * (this._numItems - fixedSize.count) + this._columnGap * (this._numItems - 1) + this._rightGap;
                } else {
                    size = this._leftGap + this._itemSize.width * this._numItems + this._columnGap * (this._numItems - 1) + this._rightGap;
                }
                break;
            case cc.Layout.Type.VERTICAL:
                if (this._customSize) {
                    const fixedSize = this._getFixedSize(null);
                    size = this._topGap + fixedSize.val + this._itemSize.height * (this._numItems - fixedSize.count) + this._lineGap * (this._numItems - 1) + this._bottomGap;
                } else {
                    size = this._topGap + this._itemSize.height * this._numItems + this._lineGap * (this._numItems - 1) + this._bottomGap;
                }
                break;
            case cc.Layout.Type.GRID:
                if (this._startAxis === cc.Layout.AxisDirection.HORIZONTAL) {
                    const rowCount = Math.ceil(this._numItems / this._colLineNum);
                    size = this._topGap + this._itemSize.height * rowCount + this._lineGap * (rowCount - 1) + this._bottomGap;
                } else {
                    const colCount = Math.ceil(this._numItems / this._colLineNum);
                    size = this._leftGap + this._itemSize.width * colCount + this._columnGap * (colCount - 1) + this._rightGap;
                }
                break;
        }
        const layout = this.content.getComponent(cc.Layout);
        if (layout) {
            layout.enabled = false;
        }
        this._allItemSize = size;
        this._allItemSizeNoEdge = this._allItemSize - (this._sizeType ? this._topGap + this._bottomGap : this._leftGap + this._rightGap);
        if (this.cyclic) {
            const viewSize = this._sizeType ? this.node.height : this.node.width;
            this._cyclicPos1 = 0;
            const remainingSize = viewSize - this._cyclicPos1;
            this._cyclicNum = Math.ceil(remainingSize / this._allItemSizeNoEdge) + 1;
            const gap = this._sizeType ? this._lineGap : this._columnGap;
            this._cyclicPos2 = this._cyclicPos1 + this._allItemSizeNoEdge + gap;
            this._cyclicAllItemSize = this._allItemSize + this._allItemSizeNoEdge * (this._cyclicNum - 1) + gap * (this._cyclicNum - 1);
            this._cycilcAllItemSizeNoEdge = this._allItemSizeNoEdge * this._cyclicNum + gap * (this._cyclicNum - 1);
        }
        this._lack = !this.cyclic && this._allItemSize < (this._sizeType ? this.node.height : this.node.width);
        const lackSize = this._lack && this.lackCenter ? 0.1 : 0;
        let contentSize = this._lack ? (this._sizeType ? this.node.height : this.node.width) - lackSize : (this.cyclic ? this._cyclicAllItemSize : this._allItemSize);
        if (contentSize < 0) {
            contentSize = 0;
        }
        if (this._sizeType) {
            this.content.height = contentSize;
        } else {
            this.content.width = contentSize;
        }
    }

    private _onScrolling(event: cc.Event | null = null) {
        if (this.frameCount == null) {
            this.frameCount = this._updateRate;
        }
        if (!this._forceUpdate && event && event.type !== "scroll-ended" && this.frameCount > 0) {
            this.frameCount--;
        } else {
            this.frameCount = this._updateRate;
            if (!this._aniDelRuning) {
                if (this.cyclic) {
                    let pos: any = this.content.getPosition();
                    pos = this._sizeType ? pos.y : pos.x;
                    const itemSize = this._allItemSizeNoEdge + (this._sizeType ? this._lineGap : this._columnGap);
                    const offset = this._sizeType ? cc.v2(0, itemSize) : cc.v2(itemSize, 0);
                    switch (this._alignCalcType) {
                        case 1:
                            if (pos > -this._cyclicPos1) {
                                this.content.x = -this._cyclicPos2;
                                if (this._scrollView.isAutoScrolling()) {
                                    this._scrollView["_autoScrollStartPosition"] = this._scrollView["_autoScrollStartPosition"].sub(offset);
                                }
                            } else if (pos < -this._cyclicPos2) {
                                this.content.x = -this._cyclicPos1;
                                if (this._scrollView.isAutoScrolling()) {
                                    this._scrollView["_autoScrollStartPosition"] = this._scrollView["_autoScrollStartPosition"].add(offset);
                                }
                            }
                            break;
                        case 2:
                            if (pos < this._cyclicPos1) {
                                this.content.x = this._cyclicPos2;
                                if (this._scrollView.isAutoScrolling()) {
                                    this._scrollView["_autoScrollStartPosition"] = this._scrollView["_autoScrollStartPosition"].add(offset);
                                }
                            } else if (pos > this._cyclicPos2) {
                                this.content.x = this._cyclicPos1;
                                if (this._scrollView.isAutoScrolling()) {
                                    this._scrollView["_autoScrollStartPosition"] = this._scrollView["_autoScrollStartPosition"].sub(offset);
                                }
                            }
                            break;
                        case 3:
                            if (pos < this._cyclicPos1) {
                                this.content.y = this._cyclicPos2;
                                if (this._scrollView.isAutoScrolling()) {
                                    this._scrollView["_autoScrollStartPosition"] = this._scrollView["_autoScrollStartPosition"].add(offset);
                                }
                            } else if (pos > this._cyclicPos2) {
                                this.content.y = this._cyclicPos1;
                                if (this._scrollView.isAutoScrolling()) {
                                    this._scrollView["_autoScrollStartPosition"] = this._scrollView["_autoScrollStartPosition"].sub(offset);
                                }
                            }
                            break;
                        case 4:
                            if (pos > -this._cyclicPos1) {
                                this.content.y = -this._cyclicPos2;
                                if (this._scrollView.isAutoScrolling()) {
                                    this._scrollView["_autoScrollStartPosition"] = this._scrollView["_autoScrollStartPosition"].sub(offset);
                                }
                            } else if (pos < -this._cyclicPos2) {
                                this.content.y = -this._cyclicPos1;
                                if (this._scrollView.isAutoScrolling()) {
                                    this._scrollView["_autoScrollStartPosition"] = this._scrollView["_autoScrollStartPosition"].add(offset);
                                }
                            }
                            break;
                    }
                }
                this._calcViewPos();
                if (this._sizeType) {
                    this.viewTop = this.viewTop;
                    this.viewBottom = this.viewBottom;
                } else {
                    this.viewRight = this.viewRight;
                    this.viewLeft = this.viewLeft;
                }
                if (this._virtual) {
                    this.displayData = [];
                    let itemPos;
                    let startIdx = 0;
                    let endIdx = this._numItems - 1;
                    if (this._customSize) {
                        let found = false;
                        for (let i = startIdx; i <= endIdx && !found; i++) {
                            itemPos = this._calcItemPos(i);
                            switch (this._align) {
                                case cc.Layout.Type.HORIZONTAL:
                                    if (itemPos.right >= this.viewLeft && itemPos.left <= this.viewRight) {
                                        this.displayData.push(itemPos);
                                    } else if (i !== 0 && this.displayData.length > 0) {
                                        found = true;
                                    }
                                    break;
                                case cc.Layout.Type.VERTICAL:
                                    if (itemPos.bottom <= this.viewTop && itemPos.top >= this.viewBottom) {
                                        this.displayData.push(itemPos);
                                    } else if (i !== 0 && this.displayData.length > 0) {
                                        found = true;
                                    }
                                    break;
                                case cc.Layout.Type.GRID:
                                    if (this._startAxis === cc.Layout.AxisDirection.HORIZONTAL) {
                                        if (itemPos.bottom <= this.viewTop && itemPos.top >= this.viewBottom) {
                                            this.displayData.push(itemPos);
                                        } else if (i !== 0 && this.displayData.length > 0) {
                                            found = true;
                                        }
                                    } else {
                                        if (itemPos.right >= this.viewLeft && itemPos.left <= this.viewRight) {
                                            this.displayData.push(itemPos);
                                        } else if (i !== 0 && this.displayData.length > 0) {
                                            found = true;
                                        }
                                    }
                                    break;
                            }
                        }
                    } else {
                        const itemWidth = this._itemSize.width + this._columnGap;
                        const itemHeight = this._itemSize.height + this._lineGap;
                        switch (this._alignCalcType) {
                            case 1:
                                startIdx = Math.floor((this.viewLeft - this._leftGap) / itemWidth) * this._colLineNum;
                                endIdx = Math.ceil((this.viewRight - this._leftGap) / itemWidth) * this._colLineNum;
                                break;
                            case 2:
                                startIdx = Math.floor((-this.viewRight - this._rightGap) / itemWidth) * this._colLineNum;
                                endIdx = Math.ceil((-this.viewLeft - this._rightGap) / itemWidth) * this._colLineNum;
                                break;
                            case 3:
                                startIdx = Math.floor((-this.viewTop - this._topGap) / itemHeight) * this._colLineNum;
                                endIdx = Math.ceil((-this.viewBottom - this._topGap) / itemHeight) * this._colLineNum;
                                break;
                            case 4:
                                startIdx = Math.floor((this.viewBottom - this._bottomGap) / itemHeight) * this._colLineNum;
                                endIdx = Math.ceil((this.viewTop - this._bottomGap) / itemHeight) * this._colLineNum;
                                break;
                        }
                        startIdx = Math.max(0, startIdx);
                        endIdx = Math.min(this._numItems - 1, endIdx);
                        for (let i = startIdx; i <= endIdx; i++) {
                            this.displayData.push(this._calcItemPos(i));
                        }
                    }
                    this._delRedundantItem();
                    if (this.displayData.length <= 0 || !this._numItems) {
                        this._lastDisplayData = [];
                        return;
                    }
                    this.firstListId = this.displayData[0].id;
                    this.displayItemNum = this.displayData.length;
                    const lastDisplayDataLength = this._lastDisplayData.length;
                    let isDifferent = this.displayItemNum !== lastDisplayDataLength;
                    if (isDifferent) {
                        if (this.frameByFrameRenderNum > 0) {
                            this._lastDisplayData.sort((a, b) => a - b);
                        }
                        isDifferent = this.firstListId !== this._lastDisplayData[0] || this.displayData[this.displayItemNum - 1].id !== this._lastDisplayData[lastDisplayDataLength - 1];
                    }
                    if (this._forceUpdate || isDifferent) {
                        if (this.frameByFrameRenderNum > 0) {
                            if (this._numItems > 0) {
                                this._updateDone ? (this._updateCounter = 0) : (this._doneAfterUpdate = true);
                                this._updateDone = false;
                            } else {
                                this._updateCounter = 0;
                                this._updateDone = true;
                            }
                        } else {
                            this._lastDisplayData = [];
                            for (let i = 0; i < this.displayItemNum; i++) {
                                this._createOrUpdateItem(this.displayData[i]);
                            }
                            this._forceUpdate = false;
                        }
                    }
                    this._calcNearestItem();
                }
            }
        }
    }

    private _calcViewPos() {
        const pos = this.content.getPosition();
        switch (this._alignCalcType) {
            case 1:
                this.elasticLeft = pos.x > 0 ? pos.x : 0;
                this.viewLeft = (pos.x < 0 ? -pos.x : 0) - this.elasticLeft;
                this.viewRight = this.viewLeft + this.node.width;
                this.elasticRight = this.viewRight > this.content.width ? Math.abs(this.viewRight - this.content.width) : 0;
                this.viewRight += this.elasticRight;
                break;
            case 2:
                this.elasticRight = pos.x < 0 ? -pos.x : 0;
                this.viewRight = (pos.x > 0 ? -pos.x : 0) + this.elasticRight;
                this.viewLeft = this.viewRight - this.node.width;
                this.elasticLeft = this.viewLeft < -this.content.width ? Math.abs(this.viewLeft + this.content.width) : 0;
                this.viewLeft -= this.elasticLeft;
                break;
            case 3:
                this.elasticTop = pos.y < 0 ? Math.abs(pos.y) : 0;
                this.viewTop = (pos.y > 0 ? -pos.y : 0) + this.elasticTop;
                this.viewBottom = this.viewTop - this.node.height;
                this.elasticBottom = this.viewBottom < -this.content.height ? Math.abs(this.viewBottom + this.content.height) : 0;
                this.viewBottom += this.elasticBottom;
                break;
            case 4:
                this.elasticBottom = pos.y > 0 ? Math.abs(pos.y) : 0;
                this.viewBottom = (pos.y < 0 ? -pos.y : 0) - this.elasticBottom;
                this.viewTop = this.viewBottom + this.node.height;
                this.elasticTop = this.viewTop > this.content.height ? Math.abs(this.viewTop - this.content.height) : 0;
                this.viewTop -= this.elasticTop;
        }
    }

    private _calcItemPos(index: number) {
        let left: number;
        let right: number;
        let top: number;
        let bottom: number;
        let x: number;
        let y: number;
        switch (this._align) {
            case cc.Layout.Type.HORIZONTAL:
                switch (this._horizontalDir) {
                    case cc.Layout.HorizontalDirection.LEFT_TO_RIGHT:
                        if (this._customSize) {
                            const fixedSize = this._getFixedSize(index);
                            left = this._leftGap + (this._itemSize.width + this._columnGap) * (index - fixedSize.count) + (fixedSize.val + this._columnGap * fixedSize.count);
                            const customWidth = this._customSize[index];
                            right = left + (customWidth > 0 ? customWidth : this._itemSize.width);
                        } else {
                            left = this._leftGap + (this._itemSize.width + this._columnGap) * index;
                            right = left + this._itemSize.width;
                        }
                        if (this.lackCenter) {
                            left -= this._leftGap;
                            left += this.content.width / 2 - this._allItemSizeNoEdge / 2;
                        }
                        x = left + this._itemTmp.anchorX * this._itemSize.width;
                        y = this._itemTmp.y;
                        return { id: index, left, right, x, y };
                    case cc.Layout.HorizontalDirection.RIGHT_TO_LEFT:
                        if (this._customSize) {
                            const fixedSize = this._getFixedSize(index);
                            right = -this._rightGap - (this._itemSize.width + this._columnGap) * (index - fixedSize.count) - (fixedSize.val + this._columnGap * fixedSize.count);
                            const customWidth = this._customSize[index];
                            left = right - (customWidth > 0 ? customWidth : this._itemSize.width);
                        } else {
                            right = -this._rightGap - (this._itemSize.width + this._columnGap) * index;
                            left = right - this._itemSize.width;
                        }
                        if (this.lackCenter) {
                            right += this._rightGap;
                            right -= this.content.width / 2 - this._allItemSizeNoEdge / 2;
                        }
                        x = left + this._itemTmp.anchorX * this._itemSize.width;
                        y = this._itemTmp.y;
                        return { id: index, left, right, x, y };
                }
                break;
            case cc.Layout.Type.VERTICAL:
                switch (this._verticalDir) {
                    case cc.Layout.VerticalDirection.TOP_TO_BOTTOM:
                        if (this._customSize) {
                            const fixedSize = this._getFixedSize(index);
                            top = -this._topGap - (this._itemSize.height + this._lineGap) * (index - fixedSize.count) - (fixedSize.val + this._lineGap * fixedSize.count);
                            const customHeight = this._customSize[index];
                            bottom = top - (customHeight > 0 ? customHeight : this._itemSize.height);
                        } else {
                            top = -this._topGap - (this._itemSize.height + this._lineGap) * index;
                            bottom = top - this._itemSize.height;
                        }
                        if (this.lackCenter) {
                            top += this._topGap;
                            top -= this.content.height / 2 - this._allItemSizeNoEdge / 2;
                        }
                        x = this._itemTmp.x;
                        y = bottom + this._itemTmp.anchorY * this._itemSize.height;
                        return { id: index, top, bottom, x, y };
                    case cc.Layout.VerticalDirection.BOTTOM_TO_TOP:
                        if (this._customSize) {
                            const fixedSize = this._getFixedSize(index);
                            bottom = this._bottomGap + (this._itemSize.height + this._lineGap) * (index - fixedSize.count) + (fixedSize.val + this._lineGap * fixedSize.count);
                            const customHeight = this._customSize[index];
                            top = bottom + (customHeight > 0 ? customHeight : this._itemSize.height);
                        } else {
                            bottom = this._bottomGap + (this._itemSize.height + this._lineGap) * index;
                            top = bottom + this._itemSize.height;
                        }
                        if (this.lackCenter) {
                            bottom -= this._bottomGap;
                            bottom += this.content.height / 2 - this._allItemSizeNoEdge / 2;
                        }
                        x = this._itemTmp.x;
                        y = bottom + this._itemTmp.anchorY * this._itemSize.height;
                        return { id: index, top, bottom, x, y };
                }
                break;
            case cc.Layout.Type.GRID:
                const row = Math.floor(index / this._colLineNum);
                switch (this._startAxis) {
                    case cc.Layout.AxisDirection.HORIZONTAL:
                        switch (this._verticalDir) {
                            case cc.Layout.VerticalDirection.TOP_TO_BOTTOM:
                                top = -this._topGap - (this._itemSize.height + this._lineGap) * row;
                                bottom = top - this._itemSize.height;
                                y = bottom + this._itemTmp.anchorY * this._itemSize.height;
                                break;
                            case cc.Layout.VerticalDirection.BOTTOM_TO_TOP:
                                bottom = this._bottomGap + (this._itemSize.height + this._lineGap) * row;
                                top = bottom + this._itemSize.height;
                                y = bottom + this._itemTmp.anchorY * this._itemSize.height;
                                break;
                        }
                        left = this._leftGap + (index % this._colLineNum) * (this._itemSize.width + this._columnGap);
                        switch (this._horizontalDir) {
                            case cc.Layout.HorizontalDirection.LEFT_TO_RIGHT:
                                x = left + this._itemTmp.anchorX * this._itemSize.width;
                                x -= this.content.anchorX * this.content.width;
                                break;
                            case cc.Layout.HorizontalDirection.RIGHT_TO_LEFT:
                                x = left + (1 - this._itemTmp.anchorX) * this._itemSize.width;
                                x -= (1 - this.content.anchorX) * this.content.width;
                                x *= -1;
                                break;
                        }
                        return { id: index, top, bottom, x, y };
                    case cc.Layout.AxisDirection.VERTICAL:
                        switch (this._horizontalDir) {
                            case cc.Layout.HorizontalDirection.LEFT_TO_RIGHT:
                                left = this._leftGap + (this._itemSize.width + this._columnGap) * row;
                                right = left + this._itemSize.width;
                                x = left + this._itemTmp.anchorX * this._itemSize.width;
                                x -= this.content.anchorX * this.content.width;
                                break;
                            case cc.Layout.HorizontalDirection.RIGHT_TO_LEFT:
                                right = -this._rightGap - (this._itemSize.width + this._columnGap) * row;
                                left = right - this._itemSize.width;
                                x = left + this._itemTmp.anchorX * this._itemSize.width;
                                x += (1 - this.content.anchorX) * this.content.width;
                                break;
                        }
                        y = -this._topGap - (index % this._colLineNum) * (this._itemSize.height + this._lineGap);
                        switch (this._verticalDir) {
                            case cc.Layout.VerticalDirection.TOP_TO_BOTTOM:
                                y -= (1 - this._itemTmp.anchorY) * this._itemSize.height;
                                y += (1 - this.content.anchorY) * this.content.height;
                                break;
                            case cc.Layout.VerticalDirection.BOTTOM_TO_TOP:
                                y -= this._itemTmp.anchorY * this._itemSize.height;
                                y += this.content.anchorY * this.content.height;
                                y *= -1;
                                break;
                        }
                        return { id: index, left, right, x, y };
                }
        }
    }

    private _calcExistItemPos(index: number) {
        const item = this.getItemByListId(index);
        if (!item) {
            return null;
        }
        let pos = { id: index, x: item.x, y: item.y, top: 0, bottom: 0, left: 0, right: 0 };
        if (this._sizeType) {
            pos.top = item.y + item.height * (1 - item.anchorY);
            pos.bottom = item.y - item.height * item.anchorY;
        } else {
            pos.left = item.x - item.width * item.anchorX;
            pos.right = item.x + item.width * (1 - item.anchorX);
        }
        return pos;
    }

    getItemPos(index: number) {
        if (this._virtual || this.frameByFrameRenderNum) {
            return this._calcItemPos(index);
        } else {
            return this._calcExistItemPos(index);
        }
    }

    private _getFixedSize(index: number | null) {
        if (!this._customSize) {
            return null;
        }
        if (index == null) {
            index = this._numItems;
        }
        let totalSize = 0;
        let count = 0;
        for (const key in this._customSize) {
            if (parseInt(key) < index) {
                totalSize += this._customSize[key];
                count++;
            }
        }
        return { val: totalSize, count };
    }

    private _onScrollBegan() {
        this._beganPos = this._sizeType ? this.viewTop : this.viewLeft;
    }

    private _onScrollEnded() {
        this.curScrollIsTouch = false;
        if (this.scrollToListId != null) {
            const item = this.getItemByListId(this.scrollToListId);
            this.scrollToListId = null;
            if (item) {
                cc.tween(item)
                    .to(0.1, { scale: 1.06 })
                    .to(0.1, { scale: 1 })
                    .start();
            }
        }
        this._onScrolling();
        if (this._slideMode !== SlideMode.ADHERING || this.adhering) {
            if (this._slideMode === SlideMode.PAGE) {
                if (this._beganPos != null && this.curScrollIsTouch) {
                    this._pageAdhere();
                } else {
                    this.adhere();
                }
            }
        } else {
            this.adhere();
        }
    }

    private _onTouchStart(event: cc.Event.EventTouch, captureListeners: any) {
        if (!this._scrollView.hasNestedViewGroup(event, captureListeners) && event.eventPhase === cc.Event.AT_TARGET && event.target === this.node) {
            let target = event.target;
            while (target._listId == null && target.parent) {
                target = target.parent;
            }
            if (target._listId != null) {
                this._scrollItem = target;
            } else {
                this._scrollItem = event.target;
            }
        }
    }

    private _onTouchUp() {
        this._scrollPos = null;
        if (this._slideMode === SlideMode.ADHERING) {
            if (this.adhering) {
                this._adheringBarrier = true;
            }
            this.adhere();
        } else if (this._slideMode === SlideMode.PAGE) {
            if (this._beganPos != null) {
                this._pageAdhere();
            } else {
                this.adhere();
            }
        }
        this._scrollItem = null;
    }

    private _onTouchCancelled(event: cc.Event.EventTouch, captureListeners: any) {
        if (!this._scrollView.hasNestedViewGroup(event, captureListeners) && !event["simulate"]) {
            this._scrollPos = null;
            if (this._slideMode === SlideMode.ADHERING) {
                if (this.adhering) {
                    this._adheringBarrier = true;
                }
                this.adhere();
            } else if (this._slideMode === SlideMode.PAGE) {
                if (this._beganPos != null) {
                    this._pageAdhere();
                } else {
                    this.adhere();
                }
            }
            this._scrollItem = null;
        }
    }

    private _onSizeChanged() {
        if (this.checkInited(false)) {
            this._onScrolling();
        }
    }

    private _onItemAdaptive(item: cc.Node) {
        if ((!this._sizeType && item.width !== this._itemSize.width) || (this._sizeType && item.height !== this._itemSize.height)) {
            if (!this._customSize) {
                this._customSize = {};
            }
            const size = this._sizeType ? item.height : item.width;
            if (this._customSize[item["_listId"]] !== size) {
                this._customSize[item["_listId"]] = size;
                this._resizeContent();
                this.updateAll();
                if (this._scrollToListId != null) {
                    this._scrollPos = null;
                    this.unschedule(this._scrollToSo);
                    this.scrollTo(this._scrollToListId, Math.max(0, this._scrollToEndTime - new Date().getTime() / 1000));
                }
            }
        }
    }

    private _pageAdhere() {
        if (this.cyclic || !(this.elasticTop > 0 || this.elasticRight > 0 || this.elasticBottom > 0 || this.elasticLeft > 0)) {
            const pos = this._sizeType ? this.viewTop : this.viewLeft;
            const distance = (this._sizeType ? this.node.height : this.node.width) * this.pageDistance;
            if (Math.abs(this._beganPos - pos) > distance) {
                switch (this._alignCalcType) {
                    case 1:
                    case 4:
                        if (this._beganPos > pos) {
                            this.prePage(0.5);
                        } else {
                            this.nextPage(0.5);
                        }
                        break;
                    case 2:
                    case 3:
                        if (this._beganPos < pos) {
                            this.prePage(0.5);
                        } else {
                            this.nextPage(0.5);
                        }
                        break;
                }
            } else if (this.elasticTop <= 0 && this.elasticRight <= 0 && this.elasticBottom <= 0 && this.elasticLeft <= 0) {
                this.adhere();
            }
            this._beganPos = null;
        }
    }

    private adhere() {
        if (this.checkInited() && !(this.elasticTop > 0 || this.elasticRight > 0 || this.elasticBottom > 0 || this.elasticLeft > 0)) {
            this.adhering = true;
            this._calcNearestItem();
            const offset = (this._sizeType ? this._topGap : this._leftGap) / (this._sizeType ? this.node.height : this.node.width);
            this.scrollTo(this.nearestListId, 0.7, offset);
        }
    }

    update() {
        if (this.frameByFrameRenderNum > 0 && !this._updateDone) {
            if (this._virtual) {
                let endIdx = this._updateCounter + this.frameByFrameRenderNum > this.displayItemNum ? this.displayItemNum : this._updateCounter + this.frameByFrameRenderNum;
                for (let i = this._updateCounter; i < endIdx; i++) {
                    const itemData = this.displayData[i];
                    if (itemData) {
                        this._createOrUpdateItem(itemData);
                    }
                }
                if (this._updateCounter >= this.displayItemNum - 1) {
                    if (this._doneAfterUpdate) {
                        this._updateCounter = 0;
                        this._updateDone = false;
                        this._doneAfterUpdate = false;
                    } else {
                        this._updateDone = true;
                        this._delRedundantItem();
                        this._forceUpdate = false;
                        this._calcNearestItem();
                        if (this.slideMode === SlideMode.PAGE) {
                            this.curPageNum = this.nearestListId;
                        }
                    }
                } else {
                    this._updateCounter += this.frameByFrameRenderNum;
                }
            } else {
                if (this._updateCounter < this._numItems) {
                    let endIdx = this._updateCounter + this.frameByFrameRenderNum > this._numItems ? this._numItems : this._updateCounter + this.frameByFrameRenderNum;
                    for (let i = this._updateCounter; i < endIdx; i++) {
                        this._createOrUpdateItem2(i);
                    }
                    this._updateCounter += this.frameByFrameRenderNum;
                } else {
                    this._updateDone = true;
                    this._calcNearestItem();
                    if (this.slideMode === SlideMode.PAGE) {
                        this.curPageNum = this.nearestListId;
                    }
                }
            }
        }
    }

    private _createOrUpdateItem(itemData: any) {
        let item: any = this.getItemByListId(itemData.id);
        if (item) {
            if (this._forceUpdate && this.renderEvent) {
                item.setPosition(cc.v2(itemData.x, itemData.y));
                this._resetItemSize(item);
                cc.Component.EventHandler.emitEvents([this.renderEvent], item, itemData.id % this._actualNumItems);
            }
        } else {
            const isFromPool = this._pool.size() > 0;
            item = isFromPool ? this._pool.get() : ResourceUtil.instantiate(this._itemTmp);
            if (!isFromPool || !cc.isValid(item)) {
                item = ResourceUtil.instantiate(this._itemTmp);
            }
            if (item["_listId"] !== itemData.id) {
                item["_listId"] = itemData.id;
                item.setContentSize(this._itemSize);
            }
            item.setPosition(cc.v2(itemData.x, itemData.y));
            this._resetItemSize(item);
            this.content.addChild(item);
            if (isFromPool && this._needUpdateWidget) {
                const widget = item.getComponent(cc.Widget);
                if (widget) {
                    widget.updateAlignment();
                }
            }
            item.setSiblingIndex(this.content.childrenCount - 1);
            let listItem = item.getComponent(ListItem);
            item.listItem = listItem;
            if (listItem) {
                listItem.listId = itemData.id;
                listItem.list = this;
                listItem._registerEvent();
            }
            cc.Component.EventHandler.emitEvents([this.renderEvent], item, itemData.id % this._actualNumItems);
        }
        this._resetItemSize(item);
        this._updateListItem(item.listItem);
        if (this._lastDisplayData.indexOf(itemData.id) < 0) {
            this._lastDisplayData.push(itemData.id);
        }
    }

    private _createOrUpdateItem2(index: number) {
        let item: cc.Node = this.content.children[index];
        if (item) {
            if (this._forceUpdate && this.renderEvent) {
                item["_listId"] = index;
                const listItem = item.getComponent(ListItem);
                if (listItem) {
                    listItem.listId = index;
                }
                cc.Component.EventHandler.emitEvents([this.renderEvent], item, index % this._actualNumItems);
            }
        } else {
            item = ResourceUtil.instantiate(this._itemTmp);
            item["_listId"] = index;
            this.content.addChild(item);
            const listItem = item.getComponent(ListItem);
            item["listItem"] = listItem;
            if (listItem) {
                listItem.listId = index;
                listItem.list = this;
                listItem.registerEvent();
            }
            cc.Component.EventHandler.emitEvents([this.renderEvent], item, index % this._actualNumItems);
        }
        this._updateListItem(item["listItem"]);
        if (this._lastDisplayData.indexOf(index) < 0) {
            this._lastDisplayData.push(index);
        }
    }

    private _updateListItem(listItem: ListItem | null) {
        if (listItem && this.selectedMode > SelectedMode.NONE) {
            const itemNode: cc.Node = listItem.node;
            switch (this.selectedMode) {
                case SelectedMode.SINGLE:
                    listItem.selected = this.selectedId === itemNode["_listId"];
                    break;
                case SelectedMode.MULT:
                    listItem.selected = this.multSelected.indexOf(itemNode["_listId"]) >= 0;
                    break;
            }
        }
    }

    private _resetItemSize(item: cc.Node) {
        // 这里可以根据需要重置 item 的大小
    }

    private _updateItemPos(item: cc.Node | number) {
        const targetItem: cc.Node = typeof item === 'number' ? this.getItemByListId(item) : item;
        if (targetItem) {
            const pos = this.getItemPos(targetItem["_listId"]);
            targetItem.setPosition(pos.x, pos.y);
        }
    }

    setMultSelected(ids: number | number[], isSelected: boolean) {
        if (this.checkInited()) {
            if (!Array.isArray(ids)) {
                ids = [ids];
            }
            if (isSelected == null) {
                this.multSelected = ids;
            } else {
                ids.forEach(id => {
                    const index = this.multSelected.indexOf(id);
                    if (isSelected && index < 0) {
                        this.multSelected.push(id);
                    } else if (!isSelected && index >= 0) {
                        this.multSelected.splice(index, 1);
                    }
                });
            }
            this._forceUpdate = true;
            this._onScrolling();
        }
    }

    getMultSelected(): number[] {
        return this.multSelected;
    }

    hasMultSelected(id: number): boolean {
        return this.multSelected && this.multSelected.indexOf(id) >= 0;
    }

    updateItem(ids: number | number[]) {
        if (this.checkInited()) {
            if (!Array.isArray(ids)) {
                ids = [ids];
            }
            ids.forEach(id => {
                const item = this.getItemByListId(id);
                if (item) {
                    cc.Component.EventHandler.emitEvents([this.renderEvent], item, id % this._actualNumItems);
                }
            });
        }
    }

    updateAll() {
        if (this.checkInited()) {
            this.numItems = this.numItems;
        }
    }

    getItemByListId(id: number): cc.Node | undefined {
        if (this.content) {
            return this.content.children.find(child => child["_listId"] === id);
        }
    }

    private _getOutsideItem(): cc.Node[] {
        return this.content.children.filter(child => !this.displayData.some(data => data.id === child["_listId"]));
    }

    private _delRedundantItem() {
        if (this._virtual) {
            const outsideItems: cc.Node[] = this._getOutsideItem();
            outsideItems.forEach(item => {
                if (!this._scrollItem || item["_listId"] !== this._scrollItem["_listId"]) {
                    item["isCached"] = true;
                    this._pool.put(item);
                    const index = this._lastDisplayData.indexOf(item["_listId"]);
                    if (index >= 0) {
                        this._lastDisplayData.splice(index, 1);
                    }
                }
            });
        } else {
            while (this.content.childrenCount > this._numItems) {
                this._delSingleItem(this.content.children[this.content.childrenCount - 1]);
            }
        }
    }

    private _delSingleItem(item: cc.Node) {
        ResourceUtil.removeFromParentAndRelease(item);
        if (item.destroy) {
            item.destroy();
        }
    }

    aniDelItem(id: number, callback: Function, animation: number) {
        if (!this.checkInited() || this.cyclic || !this._virtual) {
            return cc.error("This function is not allowed to be called!");
        }
        if (!callback) {
            return cc.error("CallFunc are not allowed to be NULL, You need to delete the corresponding index in the data array in the CallFunc!");
        }
        if (this._aniDelRuning) {
            return cc.warn("Please wait for the current deletion to finish!");
        }
        const item = this.getItemByListId(id);
        if (item) {
            const listItem = item.getComponent(ListItem);
            this._aniDelRuning = true;
            this._aniDelCB = callback;
            this._aniDelItem = item;
            this._aniDelBeforePos = item.position;
            this._aniDelBeforeScale = item.scale;
            const lastDisplayId = this.displayData[this.displayData.length - 1].id;
            const isSelected = listItem.selected;
            listItem.showAnimation(animation, () => {
                let nextId;
                if (lastDisplayId < this._numItems - 2) {
                    nextId = lastDisplayId + 1;
                }
                if (nextId != null) {
                    const newItemPos = this._calcItemPos(nextId);
                    this.displayData.push(newItemPos);
                    if (this._virtual) {
                        this._createOrUpdateItem(newItemPos);
                    } else {
                        this._createOrUpdateItem2(nextId);
                    }
                } else {
                    this._numItems--;
                }
                if (this.selectedMode === SelectedMode.SINGLE) {
                    if (isSelected) {
                        this._selectedId = -1;
                    } else if (this._selectedId - 1 >= 0) {
                        this._selectedId--;
                    }
                } else if (this.selectedMode === SelectedMode.MULT && this.multSelected.length) {
                    const index = this.multSelected.indexOf(id);
                    if (index >= 0) {
                        this.multSelected.splice(index, 1);
                    }
                    this.multSelected = this.multSelected.map(selectedId => (selectedId >= id ? selectedId - 1 : selectedId));
                }
                if (this._customSize) {
                    if (this._customSize[id]) {
                        delete this._customSize[id];
                    }
                    const newCustomSize = {};
                    for (const key in this._customSize) {
                        const size = this._customSize[key];
                        const newKey = parseInt(key) - (parseInt(key) >= id ? 1 : 0);
                        newCustomSize[newKey] = size;
                    }
                    this._customSize = newCustomSize;
                }
                if (nextId == null) {
                    nextId = lastDisplayId;
                }
                this._aniDelRuning = false;
                callback(id);
                this._aniDelCB = null;
            }, true);
        } else {
            callback(id);
        }
    }

    scrollTo(id: number, time: number = 0.5, offset: number | null = null, animate: boolean = false) {
        if (this.checkInited(false)) {
            if (time < 0) {
                time = 0;
            }
            if (id < 0) {
                id = 0;
            } else if (id >= this._numItems) {
                id = this._numItems - 1;
            }
            if (!this._virtual && this._layout && this._layout.enabled) {
                this._layout.updateLayout();
            }
            const itemPos = this.getItemPos(id);
            if (!itemPos) {
                return false;
            }
            let targetPos: cc.Vec2;
            switch (this._alignCalcType) {
                case 1:
                    targetPos = cc.v2(itemPos.left - (offset != null ? this.node.width * offset : this._leftGap), 0);
                    break;
                case 2:
                    targetPos = cc.v2(itemPos.right - this.node.width + (offset != null ? this.node.width * offset : this._rightGap) + this.content.width, 0);
                    break;
                case 3:
                    targetPos = cc.v2(0, -itemPos.top + (offset != null ? this.node.height * offset : this._topGap));
                    break;
                case 4:
                    targetPos = cc.v2(0, -itemPos.bottom - this.node.height - (offset != null ? this.node.height * offset : this._bottomGap) + this.content.height);
                    break;
            }
            const currentPos = this.content.getPosition();
            const currentPosValue = Math.abs(this._sizeType ? currentPos.y : currentPos.x);
            const targetPosValue = this._sizeType ? targetPos.y : targetPos.x;
            if (Math.abs((this._scrollPos != null ? this._scrollPos : currentPosValue) - targetPosValue) > 0.5) {
                this._scrollView.scrollToOffset(targetPos, time);
                this._scrollToListId = id;
                this._scrollToEndTime = new Date().getTime() / 1000 + time;
                this._scrollToSo = this.scheduleOnce(() => {
                    if (!this._adheringBarrier) {
                        this.adhering = this._adheringBarrier = false;
                    }
                    this._scrollPos = this._scrollToListId = this._scrollToEndTime = this._scrollToSo = null;
                    if (animate) {
                        const item = this.getItemByListId(id);
                        if (item) {
                            cc.tween(item)
                                .to(0.1, { scale: 1.05 })
                                .to(0.1, { scale: 1 })
                                .start();
                        }
                    }
                }, time + 0.1);
                if (time <= 0) {
                    this._onScrolling();
                }
            }
        }
    }

    private _calcNearestItem() {
        this.nearestListId = null;
        if (this._virtual) {
            this._calcViewPos();
        }
        const viewTop = this.viewTop;
        const viewRight = this.viewRight;
        const viewBottom = this.viewBottom;
        const viewLeft = this.viewLeft;
        let found = false;
        for (let i = 0; i < this.content.childrenCount && !found; i += this._colLineNum) {
            const itemPos = this._virtual ? this.displayData[i] : this._calcExistItemPos(i);
            if (itemPos) {
                const itemCenter = this._sizeType ? (itemPos.top + itemPos.bottom) / 2 : (itemPos.left + itemPos.right) / 2;
                switch (this._alignCalcType) {
                    case 1:
                        if (itemPos.right >= viewLeft) {
                            this.nearestListId = itemPos.id;
                            if (viewLeft > itemCenter) {
                                this.nearestListId += this._colLineNum;
                            }
                            found = true;
                        }
                        break;
                    case 2:
                        if (itemPos.left <= viewRight) {
                            this.nearestListId = itemPos.id;
                            if (viewRight < itemCenter) {
                                this.nearestListId += this._colLineNum;
                            }
                            found = true;
                        }
                        break;
                    case 3:
                        if (itemPos.bottom <= viewTop) {
                            this.nearestListId = itemPos.id;
                            if (viewTop < itemCenter) {
                                this.nearestListId += this._colLineNum;
                            }
                            found = true;
                        }
                        break;
                    case 4:
                        if (itemPos.top >= viewBottom) {
                            this.nearestListId = itemPos.id;
                            if (viewBottom > itemCenter) {
                                this.nearestListId += this._colLineNum;
                            }
                            found = true;
                        }
                        break;
                }
            }
        }
        const lastItemPos = this._virtual ? this.displayData[this.displayItemNum - 1] : this._calcExistItemPos(this._numItems - 1);
        if (lastItemPos && lastItemPos.id === this._numItems - 1) {
            const lastItemCenter = this._sizeType ? (lastItemPos.top + lastItemPos.bottom) / 2 : (lastItemPos.left + lastItemPos.right) / 2;
            switch (this._alignCalcType) {
                case 1:
                    if (viewRight > lastItemCenter) {
                        this.nearestListId = lastItemPos.id;
                    }
                    break;
                case 2:
                    if (viewLeft < lastItemCenter) {
                        this.nearestListId = lastItemPos.id;
                    }
                    break;
                case 3:
                    if (viewBottom < lastItemCenter) {
                        this.nearestListId = lastItemPos.id;
                    }
                    break;
                case 4:
                    if (viewTop > lastItemCenter) {
                        this.nearestListId = lastItemPos.id;
                    }
                    break;
            }
        }
    }

    prePage(time: number = 0.5) {
        if (this.checkInited()) {
            this.skipPage(this.curPageNum - 1, time);
        }
    }

    nextPage(time: number = 0.5) {
        if (this.checkInited()) {
            this.skipPage(this.curPageNum + 1, time);
        }
    }

    skipPage(pageNum: number, time: number) {
        if (this.checkInited()) {
            if (this._slideMode !== SlideMode.PAGE) {
                return cc.error("This function is not allowed to be called, Must SlideMode = PAGE!");
            }
            if (pageNum < 0 || pageNum >= this._numItems || this.curPageNum === pageNum) {
                return;
            }
            this.curPageNum = pageNum;
            if (this.pageChangeEvent) {
                cc.Component.EventHandler.emitEvents([this.pageChangeEvent], pageNum);
            }
            this.scrollTo(pageNum, time);
        }
    }

    calcCustomSize(numItems: number) {
        if (this.checkInited()) {
            if (!this._itemTmp) {
                return cc.error("Unset template item!");
            }
            if (!this.renderEvent) {
                return cc.error("Unset Render-Event!");
            }
            this._customSize = {};
            const tempItem = ResourceUtil.instantiate(this._itemTmp);
            this.content.addChild(tempItem);
            for (let i = 0; i < numItems; i++) {
                cc.Component.EventHandler.emitEvents([this.renderEvent], tempItem, i);
                if (tempItem.height !== this._itemSize.height || tempItem.width !== this._itemSize.width) {
                    this._customSize[i] = this._sizeType ? tempItem.height : tempItem.width;
                }
            }
            if (!Object.keys(this._customSize).length) {
                this._customSize = null;
            }
            ResourceUtil.removeFromParentAndRelease(tempItem);
            if (tempItem.destroy) {
                tempItem.destroy();
            }
            return this._customSize;
        }
    }
}
